/*
 * Copyright (c) 2011 Imre Fazekas.
 * All rights reserved.
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.
 *
 * Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 * Neither the name of the Brillien nor the names of its
 * terms and concepts may be used to endorse or promote products derived from this
 * software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

package com.vii.brillien.core.component;

import com.vii.brillien.core.component.characteristicFunction.NullCharacteristicFunction;
import com.vii.brillien.ignition.transport.BrillienCommunication;
import com.vii.brillien.kernel.BrillienException;
import com.vii.brillien.kernel.axiom.atomic.Presence;
import com.vii.brillien.kernel.axiom.atomic.PresenceManager;
import com.vii.brillien.kernel.axiom.atomic.Unit;
import com.vii.brillien.kernel.axiom.atomic.component.CharacteristicFunction;
import com.vii.brillien.kernel.axiom.atomic.component.EquivalenceRelation;
import com.vii.brillien.kernel.axiom.atomic.component.OrderRelation;
import com.vii.streamline.annotation.P;
import com.vii.streamline.structures.collections.InnerList;

import java.util.*;
import java.util.concurrent.ConcurrentLinkedQueue;

/**
 * Specialized SuperPresence type to provide Znit implementation
 */
public class SuperUnit <S extends Presence, R> extends SuperPresence<R> implements Unit<S,BrillienCommunication,R> {

    /**
     * Collection of the subunits or subsets
     */
    protected ConcurrentLinkedQueue<Unit>       subUnits;

    /**
     * Collection of contained presences
     */
    protected ConcurrentLinkedQueue<Presence>   presences;

    /**
     * Charecteristic function of this set
     */
    protected CharacteristicFunction characteristicFunction;


    @Override
    public <T> CharacteristicFunction<S,BrillienCommunication,T> getCharacteristicFunction() {
        return characteristicFunction;
    }

    @Override
    public <T> void setCharacteristicFunction(CharacteristicFunction<S,BrillienCommunication, T> characteristicFunction) {
        this.characteristicFunction = characteristicFunction;
    }

    @Override
    public Collection<Unit> getSubUnits() {
        return subUnits;
    }

    @Override
    public Unit<S,BrillienCommunication, R> addSubUnits(Unit... ses) throws BrillienException {
        systemlog( "Adding subunits to:: " + this.getName() );
        for( Unit unit : ses)
            if( !subUnits.contains( unit ) ){
                subUnits.add( unit );
                unit.addSuperUnits( this );
            }
        return this;
    }

    @Override
    public Unit<S,BrillienCommunication, R> removeSubUnits(Unit... ses) throws BrillienException {
        systemlog( "Removing subunits to:: " + this.getName() );
        for( Unit unit : ses){
            subUnits.remove( unit );
            unit.removeSuperUnits( this );
        }
        return this;
    }

    @Override
    public void clearSubUnits() throws BrillienException {
        subUnits.clear( );
    }

    @Override
    public Collection<Presence> getPresences() {
        return presences;
    }

    @Override
    public final void scrutinise( Presence p ) throws BrillienException {
        characteristicFunction.inspect( p );
        for( Unit c : superUnits ){
            c.scrutinise( p );
        }
    }

    @Override
    public void addPresences(S... ses) throws BrillienException {
        systemlog( "Adding presences to:: " + this.getName() );
        for( Presence sing : ses){
            if( !presences.contains( sing ) ){
                scrutinise(sing);
                sing.addSuperUnits( this );
                presences.add( sing );
                systemlog( "New presence added:::" + sing.getName()+ (sing instanceof PresenceManager ? " manager " : "") + " in flow " + sing.getFlowID() + " to::" + this.getName() + " Instance count:" + presences.size() );
            }
        }
    }

    @Override
    public void removePresences(S... ses) throws BrillienException {
        systemlog( "Removing presences from:: " + this.getName() );
        for( Presence sing : ses){
            if( presences.contains( sing ) ){
                presences.remove( sing );
                sing.removeSuperUnits( this );
                systemlog( "Presence removed:::" + sing.getName() + " in flow " + sing.getFlowID() + " to::" + this.getName() + " Instance count:" + presences.size() );
            }
        }
    }

    @Override
    public void clearPresences() throws BrillienException {
        presences.clear();
    }

    @Override
    public Unit<S,BrillienCommunication, R> subset(CharacteristicFunction<S,BrillienCommunication, R> function) throws BrillienException {
        Unit u = (Unit)this.getPresenceManager().getInstance( this.getFlowID() );
        u.setCharacteristicFunction( function );
        for( Unit c : subUnits )
            u.addSubUnits( c );
        for( Presence p : presences )
            u.addPresences( p );
        return u;
    }

    @Override
    public Unit<S,BrillienCommunication, R> union(Unit<S,BrillienCommunication, R> unit) throws BrillienException {
        Unit u = (Unit)this.getPresenceManager().getInstance( this.getFlowID() );
        
        for( Unit c : subUnits )
            u.addSubUnits( c );
        for( Presence p : presences )
            u.addPresences( p );

        for( Unit c : unit.getSubUnits() )
            u.addSubUnits( c );
        for( Presence p : unit.getPresences() )
            u.addPresences( p );

        return u;
    }

    @Override
    public Unit<S,BrillienCommunication, R> intersect(Unit<S,BrillienCommunication,R> unit) throws BrillienException {
        Unit u = (Unit)this.getPresenceManager().getInstance( this.getFlowID() );

        for( Unit c : unit.getSubUnits() )
            if( subUnits.contains( c ) )
                u.addSubUnits( c );
        for( Presence p : unit.getPresences() )
            if( presences.contains( p ) )
                u.addPresences( p );

        return u;
    }

    @Override
    public Unit<S,BrillienCommunication, R> complement(Unit<S,BrillienCommunication,R> unit) throws BrillienException {
        return union(unit).complement( intersect(unit) );
    }

    @Override
    public Unit<S,BrillienCommunication, R> symmetricDifference( Unit<S,BrillienCommunication,R> unit ) throws BrillienException{
        Unit u = (Unit)this.getPresenceManager().getInstance( this.getFlowID() );

        return u;
    }

    protected void checkClass( EquivalenceRelation<S,BrillienCommunication,R> function, LinkedList<Unit> classes, S p) throws BrillienException {
        for( Unit classSet : classes )
            if( function.isEquivalent( (S)(classSet.getPresences().iterator().next()), p ) ){
                classSet.addPresences( p ); return;
            }

        Unit u = (Unit)this.getPresenceManager().getInstance( this.getFlowID() );
        u.addPresences( p );
        classes.add( u );
    }

    @Override
    public Collection<Unit> classify( EquivalenceRelation<S,BrillienCommunication,R> function ) throws BrillienException{
        LinkedList<Unit> classes = new LinkedList<Unit>();

        for( Unit c : subUnits ){
            checkClass( function, classes, (S) c);
        }
        for( Presence p : presences ){
            checkClass( function, classes, (S) p);
        }

        return classes;
    }

    @Override
    public Collection<S> order( OrderRelation<S,BrillienCommunication,R> function ) throws BrillienException{
        LinkedList<S> classes = new LinkedList<S>();

        for( Unit c : subUnits )
            classes.add( (S) c );
        for( Presence p : presences )
            classes.add( (S) p );

        Collections.sort( classes, function );

        return classes;
    }

    @Override
    public boolean isSubSet(Unit u) {
        if( this.equals( u ) )
            return true;
        for( Unit c : subUnits ){
            if( c.isSubSet(u) )
                return true;
        }
        return false;
    }

    @Override
    public void communicationUnitReceived(BrillienCommunication message) throws BrillienException {
        for( Presence sing : presences){
            sing.communicationReceived( message );
        }
        for( Unit u : subUnits){
            u.communicationUnitReceived( message );
        }
    }

    @Override
    public void retrieveUnit() throws BrillienException {
        retrieve();

        for( Presence sing : presences){
            if( sing instanceof Unit )
                ((Unit)sing).retrieveUnit( );
            else
                sing.retrieve( );
        } presences.clear();

        for( Unit u : subUnits){
            u.retrieveUnit();
        } subUnits.clear();
    }


    @Override
    public boolean hasUnitActivity() {
        for( Presence sing : presences){
            if( sing.hasActivity() )
                return true;
        }
        for( Unit u : subUnits){
            if( u.hasUnitActivity() )
                return true;
        }
        return false;
    }

    @Override
    public void activateUnitMessaging() throws BrillienException {
        for( Presence sing : presences){
            sing.activateMessaging( );
        }
        for( Unit u : subUnits){
            u.activateUnitMessaging( );
        }
        activateMessaging();
    }

    @Override
    public void passivateUnitMessaging() throws BrillienException {
        for( Presence sing : presences){
            sing.passivateMessaging( );
        }
        for( Unit u : subUnits){
            u.passivateUnitMessaging( );
        }
        passivateMessaging();
    }

    @Override
    public void activateUnitCallable() throws BrillienException {
        for( Presence sing : presences){
            sing.activateCallable( );
        }
        for( Unit u : subUnits){
            u.activateUnitCallable( );
        }
        activateCallable();
    }

    @Override
    public void passivateUnitCallable() throws BrillienException {
        for( Presence sing : presences){
            sing.passivateCallable( );
        }
        for( Unit u : subUnits){
            u.passivateUnitCallable( );
        }
        passivateCallable();
    }

    @Override
    public String printUnitErrorStates( ){
        StringBuilder sb = new StringBuilder();
        for( Presence sing : presences){
            sb.append( sing.printErrorStates() );
        }
        for( Unit u : subUnits){
            sb.append( u.printUnitErrorStates() );
        };
        return this.printErrorStates() + " " + sb.toString();
    }

    @Override
    public boolean isUnitInValidStates(){
        for( Presence sing : presences){
            if( !sing.isInValidStates( ) )
                return false;
        }
        for( Unit u : subUnits){
            if( !u.isUnitInValidStates( ) )
                return false;
        }
        return isInValidStates();
    }

    @Override
    public boolean isUnitInExitState(){
        for( Presence sing : presences){
            if( !sing.isInExitState( ) )
                return false;
        }
        for( Unit u : subUnits){
            if( !u.isUnitInExitState( ) )
                return false;
        }
        return isInExitState();
    }

    @Override
    public boolean isUnitWaitingForResponse(){
        for( Presence sing : presences){
            if( sing.isWaitingForResponse() )
                return true;
        }
        for( Unit u : subUnits){
            if( u.isUnitWaitingForResponse( ) )
                return true;
        } 
        return isWaitingForResponse();
    }

    @Override
    public void activateUnitAll( ) throws BrillienException{
        systemlog( "activating all in " + this.getName() );
        activateUnitCallable();
        activateUnitMessaging();
    }

    @Override
    public void passivateUnitAll( ) throws BrillienException{
        systemlog( "passivating all in " + this.getName() );
        passivateUnitCallable();
        passivateUnitMessaging();
    }

    @Override
    public void reset(){
        super.reset();

        subUnits = new ConcurrentLinkedQueue<Unit>();
        presences = new ConcurrentLinkedQueue<Presence>();

        characteristicFunction = new NullCharacteristicFunction<S, Object>();
    }


    // --------------------------------------------------------------------------------
    // --------------------------------------------------------------------------------
    //              UNIT-WIDE MESSAGE PROCESSOR METHODS
    // --------------------------------------------------------------------------------
    // --------------------------------------------------------------------------------

    public List processUnitGet( String to, String message, Map<String, Object> parameters ) throws BrillienException{
        InnerList res = new InnerList();

        for( Presence p : presences )
            res.add( sendGet( p.getName(), message, parameters ) );
        for( Unit u : subUnits ){
            BrillienCommunication c = sendUnitGet( u.getName(), message, parameters );
            List l = c.acquireResponse( List.class );
            res.add( l );
        }

        return res;
    }

    public List processUnitTGet( int timeout, String to, String message, Map<String, Object> parameters ) throws BrillienException{
        InnerList res = new InnerList();

        for( Presence p : presences )
            res.add( sendGet( timeout, p.getName(), message, parameters ) );
        for( Unit u : subUnits ){
            BrillienCommunication c = sendUnitGet( timeout, u.getName(), message, parameters );
            List l = c.acquireResponse( List.class );
            res.add( l );
        }

        return res;
    }

    public void processUnitAGet( @P(name="to") String to, @P(name="message") String message, @P(name="redirectMessage") String redirectMessage, @P(name="parameters") Map<String, Object> parameters ) throws BrillienException{
        BrillienCommunication m = getSharedMessage();

        for( Presence p : presences )
            sendDelegatedSet( p.getName(), message, m.getSender(), redirectMessage, parameters );
        for( Unit u : subUnits )
            sendUnitDelegatedSet( u.getName(), message, m.getSender(), redirectMessage, parameters );
    }

    public void processUnitDelegatedSet( @P(name="to") String to, @P(name="message") String message, @P(name="redirectEntityName") String redirectEntityName, @P(name="redirectMessage") String redirectMessage, @P(name="parameters") Map<String, Object> parameters ) throws BrillienException{
        for( Presence p : presences )
            sendDelegatedSet( p.getName(), message, redirectEntityName, redirectMessage, parameters );
        for( Unit u : subUnits )
            sendUnitDelegatedSet( u.getName(), message, redirectEntityName, redirectMessage, parameters );
    }

    public void processUnitSet( @P(name="to") String to, @P(name="message") String message, @P(name="parameters") Map<String, Object> parameters ) throws BrillienException{
        for( Presence p : presences )
            sendSet( p.getName(), message, parameters );
        for( Unit u : subUnits )
            sendUnitSet( u.getName(), message, parameters );
    }



    @Override
    public boolean hasMessageProcessorReturnValue( String messageProcessorName ) throws BrillienException{
        try{
            return super.hasMessageProcessorReturnValue( messageProcessorName );
        } catch( BrillienException e ){}
        if( presences.size() > 0 )
            try{
                return presences.peek().hasMessageProcessorReturnValue( messageProcessorName );
            } catch( BrillienException e ){}
        if( subUnits.size() > 0 )
            try{
                return subUnits.peek().hasMessageProcessorReturnValue( messageProcessorName );
            } catch( BrillienException e ){}
        throw new BrillienException( "MessageProcessor does not exist!" );
    }

    @Override
    public List<String> getMessageProcessorParameterNames( String messageProcessorName ) throws BrillienException{
        try{
            return super.getMessageProcessorParameterNames( messageProcessorName );
        } catch( BrillienException e ){}
        if( presences.size() > 0 )
            try{
                return presences.peek().getMessageProcessorParameterNames( messageProcessorName );
            } catch( BrillienException e ){}
        if( subUnits.size() > 0 )
            try{
                return subUnits.peek().getMessageProcessorParameterNames( messageProcessorName );
            } catch( BrillienException e ){}
        throw new BrillienException( "MessageProcessor does not exist!" );
    }

    @Override
    public List<String> getMessageProcessorParameterTypes( String messageProcessorName ) throws BrillienException{
        try{
            return super.getMessageProcessorParameterTypes( messageProcessorName );
        } catch( BrillienException e ){}
        if( presences.size() > 0 )
            try{
                return presences.peek().getMessageProcessorParameterTypes( messageProcessorName );
            } catch( BrillienException e ){}
        if( subUnits.size() > 0 )
            try{
                return subUnits.peek().getMessageProcessorParameterTypes( messageProcessorName );
            } catch( BrillienException e ){}
        throw new BrillienException( "MessageProcessor does not exist!" );
    }

}
